package gui;

import engine.Edge;
import engine.Node;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.io.Serializable;
import javax.swing.JPanel;

/**
 * A panel that displays the map with the units.
 * @author Johnny
 */
public class MapPanel extends JPanel implements Serializable {

    private Edge edges[];
    private Node nodes[];
    private float wRatio;
    private float hRatio;
    private Dimension dim;
    private Image background;
    private Node curUnits[];
    private final int STAT_WIDTH = 8; // The width of a station
    private final int NODE_WIDTH = 5;
    private final int UNIT_WIDTH = 8;
    private Image mapImage;
    private Image resizedMapImage;
    private boolean drawStationName;

    /**
     * Draw with the properties of a node.
     */
    public final int DRAW_NODE = 0;
    /**
     * Draw with the properties of a station.
     */
    public final int DRAW_STAT = 1;
    /**
     * Draw with the properties of a unit.
     */
    public final int DRAW_UNIT = 2;

    public MapPanel setEdges(Edge[] edges) {
        this.edges = edges;
        return this;
    }

    public MapPanel setNodes(Node[] nodes) {
        this.nodes = nodes;
        return this;
    }

    public MapPanel() {
        drawStationName = true;
        edges = new Edge[0];
        nodes = new Node[0];
        dim = new Dimension(680, 490);
        curUnits = new Node[0];
        mapImage = null;
        resizedMapImage = null;
        refreshRatios();
    }

    public void setMapImage(Image mapImage) {
        this.mapImage = mapImage;
        resizedMapImage = mapImage.getScaledInstance(dim.width, dim.height, Image.SCALE_SMOOTH);
    }

    public MapPanel setDim(Dimension dim) {
        this.dim = dim;
        refreshRatios();
        return this;
    }

    public Dimension getDim() {
        return dim;
    }

    public void dispUnits(Node curUnits[]) {
        this.curUnits = curUnits;
        repaint();
    }

    /**
     * Refreshes the internal image background with edges.
     */
    public void refreshImage() {
        background = createImage(dim.width, dim.height);
        Graphics g = background.getGraphics();
        g.drawImage(resizedMapImage, 0, 0, this);
        g.setColor(Color.black);
        // Draw edges.
        for (Edge edge : edges) {
            drawEdge(edge, g);
        }
        repaint();
    }

    /**
     * Refreshes the internal image background width edges and nodes.
     */
    public void refreshFullImage() {
        drawStationName = false;
        refreshImage();
        drawStationName = true;
        Graphics g = background.getGraphics();
        for (Node node : nodes) {
            drawNodeInfo(node, g, DRAW_NODE);
        }
        repaint();
    }

    /**
     * Draws an edge to the specified graphics.
     * @param edge the edge to be drawned.
     * @param g the graphics which will do the drawing.
     * @param wRatio the width ratio which will be multiplied with the x coordinate.
     * @param hRatio the height ratio.
     */
    private void drawEdge(Edge edge, Graphics g) {
        Color prevColor = g.getColor();
        Node node1, node2;
        int x1, x2, y1, y2;
        g.setColor(edge.getColor());
        node1 = edge.getStartNode();
        for (Node middleNode : edge.getMiddleNodes()) {
            x1 = realWidth(node1.getX());
            y1 = realHeight(node1.getY());
            x2 = realWidth(middleNode.getX());
            y2 = realHeight(middleNode.getY());
            g.drawLine(x1, y1, x2, y2);
            node1 = middleNode;
        }
        node2 = edge.getStopNode();
        x1 = realWidth(node1.getX());
        y1 = realHeight(node1.getY());
        x2 = realWidth(node2.getX());
        y2 = realHeight(node2.getY());
        g.drawLine(x1, y1, x2, y2);

        g.setColor(prevColor);

        drawNodeInfo(edge.getStartNode(), g, DRAW_STAT);
        drawNodeInfo(edge.getStopNode(), g, DRAW_STAT);
    }

    /**
     * Draws a node and it's name.
     * @param node the node to be drawn.
     * @param g the graphics used for the drawing.
     * @param prop the properties (DRAW_NODE, DRAW_START, DRAW_UNIT, etc.)
     */
    private void drawNodeInfo(Node node, Graphics g, int prop) {
        int width;
        switch (prop) {
            case DRAW_UNIT:
                width = UNIT_WIDTH;
                break;
            case DRAW_STAT:
                width = STAT_WIDTH;
                break;
            default:
                width = NODE_WIDTH;
                break;
        }
        int x = realWidth(node.getX()) - (width / 2);
        int y = realHeight(node.getY()) - (width / 2);
        if (prop == DRAW_UNIT) {
            Color color = g.getColor();
            g.setColor(Color.red);
            g.fillOval(x, y, width, width);
            g.drawString(node.getName(), x, y - width/2);
            g.setColor(color);
        } else {
            g.fillRect(x, y, width, width);
            if (prop != DRAW_STAT || drawStationName)
                g.drawString(node.getName(), x, y - width/2);
        }
    }

    private void refreshRatios() {
        wRatio = (float)dim.width / 100;
        hRatio = (float)dim.height / 100;
        if (mapImage != null) {
            resizedMapImage = mapImage.getScaledInstance(dim.width, dim.height, Image.SCALE_SMOOTH);
        }
    }

    /**
     * Calculates the real height of a coordinate on this panel.
     * @param height the abstract height (0..100).
     * @return the real height of a coordinate on this panel.
     */
    private int realHeight(float height) {
        return Math.round(height * hRatio);
    }

    /**
     * Calculates the real width of a coordinate on this panel.
     * @param height the abstract width (0..100).
     * @return the real height of a coordinate on this panel.
     */
    private int realWidth(float width) { 
        return Math.round(width * wRatio);
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(background, 0, 0, this);

        for (Node node : curUnits) {
            drawNodeInfo(node, g, DRAW_UNIT);
        }
    }
}
